/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.rim;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.Reader;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.common.GlobalHistogramBinarizer;
import com.google.zxing.client.j2me.LCDUIImageLuminanceSource;
import com.google.zxing.client.rim.persistence.AppSettings;
import com.google.zxing.client.rim.persistence.history.DecodeHistory;
import com.google.zxing.client.rim.persistence.history.DecodeHistoryItem;
import com.google.zxing.client.rim.util.Log;
import com.google.zxing.client.rim.util.ReasonableTimer;
import com.google.zxing.client.rim.util.URLDecoder;
import net.rim.blackberry.api.browser.Browser;
import net.rim.blackberry.api.browser.BrowserSession;
import net.rim.device.api.ui.DrawStyle;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.container.DialogFieldManager;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.ui.container.PopupScreen;
import net.rim.device.api.ui.container.VerticalFieldManager;
import javax.microedition.io.Connector;
import javax.microedition.io.file.FileConnection;
import javax.microedition.lcdui.Image;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
/**
* The main appication menu screen.
*
* This code was contributed by LifeMarks.
*
* @author Matt York (matt@lifemarks.mobi)
*/
final class ZXingLMMainScreen extends MainScreen {
private final ZXingUiApplication app;
private final QRCapturedJournalListener imageListener;
private PopupScreen popup;
private final Reader reader;
private final Hashtable readerHints;
ZXingLMMainScreen() {
super(DEFAULT_MENU | DEFAULT_CLOSE);
setTitle(new LabelField("ZXing", DrawStyle.ELLIPSIS | USE_ALL_WIDTH));
setChangeListener(null);
Manager vfm = new VerticalFieldManager(USE_ALL_WIDTH);
FieldChangeListener buttonListener = new ButtonListener();
//0
Field snapButton = new ButtonField("Snap", FIELD_HCENTER | ButtonField.CONSUME_CLICK | USE_ALL_WIDTH);
snapButton.setChangeListener(buttonListener);
vfm.add(snapButton);
//1
Field historyButton = new ButtonField("History", FIELD_HCENTER | ButtonField.CONSUME_CLICK);
historyButton.setChangeListener(buttonListener);
vfm.add(historyButton);
//2
Field settingsButton = new ButtonField("Settings", FIELD_HCENTER | ButtonField.CONSUME_CLICK);
settingsButton.setChangeListener(buttonListener);
vfm.add(settingsButton);
//3
Field aboutButton = new ButtonField("About", FIELD_HCENTER | ButtonField.CONSUME_CLICK);
aboutButton.setChangeListener(buttonListener);
vfm.add(aboutButton);
//4
Field helpButton = new ButtonField("Help", FIELD_HCENTER | ButtonField.CONSUME_CLICK);
helpButton.setChangeListener(buttonListener);
vfm.add(helpButton);
vfm.setChangeListener(null);
add(vfm);
app = (ZXingUiApplication) UiApplication.getUiApplication();
imageListener = new QRCapturedJournalListener(this);
reader = new MultiFormatReader();
readerHints = new Hashtable(1);
readerHints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
}
/**
* Handles the newly created file. If the file is a jpg image, from the camera, the images is assumed to be
* a qrcode and decoding is attempted.
*/
void imageSaved(String imagePath) {
Log.info("Image saved: " + imagePath);
app.removeFileSystemJournalListener(imageListener);
if (imagePath.endsWith(".jpg") && imagePath.indexOf("IMG") >= 0) // a blackberry camera image file
{
Log.info("imageSaved - Got file: " + imagePath);
Camera.getInstance().exit();
Log.info("camera exit finished");
app.requestForeground();
DialogFieldManager manager = new DialogFieldManager();
popup = new PopupScreen(manager);
manager.addCustomField(new LabelField("Decoding image..."));
app.pushScreen(popup); // original
Log.info("started progress screen.");
Runnable fct = new FileConnectionThread(imagePath);
Log.info("Starting file connection thread.");
app.invokeLater(fct);
Log.info("Finished file connection thread.");
} else {
Log.error("Failed to locate camera image.");
}
}
/**
* Closes the application and persists all required data.
*/
public void close() {
app.removeFileSystemJournalListener(imageListener);
DecodeHistory.getInstance().persist();
super.close();
}
/**
* This method is overriden to remove the 'save changes' dialog when exiting.
*/
public boolean onSavePrompt() {
setDirty(false);
return true;
}
/**
* Listens for selected buttons and starts the required screen.
*/
private final class ButtonListener implements FieldChangeListener {
public void fieldChanged(Field field, int context) {
Log.debug("*** fieldChanged: " + field.getIndex());
switch (field.getIndex()) {
case 0: // snap
try {
app.addFileSystemJournalListener(imageListener);
Camera.getInstance().invoke(); // start camera
return;
}
catch (Exception e) {
Log.error("!!! Problem invoking camera.!!!: " + e);
}
break;
case 1: // history
app.pushScreen(new HistoryScreen());
break;
case 2: // settings
app.pushScreen(new SettingsScreen());
break;
case 3: //about
app.pushScreen(new AboutScreen());
break;
case 4: //help
app.pushScreen(new HelpScreen());
break;
}
}
}
/**
* Thread that decodes the newly created image. If the image is successfully decoded and the data is a URL,
* the browser is invoked and pointed to the given URL.
*/
private final class FileConnectionThread implements Runnable {
private final String imagePath;
private FileConnectionThread(String imagePath) {
this.imagePath = imagePath;
}
public void run() {
FileConnection file = null;
InputStream is = null;
Image capturedImage = null;
try {
file = (FileConnection) Connector.open("file://" + imagePath, Connector.READ_WRITE);
is = file.openInputStream();
capturedImage = Image.createImage(is);
} catch (IOException e) {
Log.error("Problem creating image: " + e);
removeProgressBar();
invalidate();
showMessage("An error occured processing the image.");
return;
} finally {
try {
if (is != null) {
is.close();
}
if (file != null && file.exists()) {
if (file.isOpen()) {
file.delete();
file.close();
}
Log.info("Deleted image file.");
}
} catch (IOException ioe) {
Log.error("Error while closing file: " + ioe);
}
}
if (capturedImage != null) {
Log.info("Got image...");
LuminanceSource source = new LCDUIImageLuminanceSource(capturedImage);
BinaryBitmap bitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source));
Result result;
ReasonableTimer decodingTimer = null;
try {
decodingTimer = new ReasonableTimer();
Log.info("Attempting to decode image...");
result = reader.decode(bitmap, readerHints);
decodingTimer.finished();
} catch (ReaderException e) {
Log.error("Could not decode image: " + e);
decodingTimer.finished();
removeProgressBar();
invalidate();
boolean showResolutionMsg =
!AppSettings.getInstance().getBooleanItem(AppSettings.SETTING_CAM_RES_MSG).booleanValue();
if (showResolutionMsg) {
showMessage("A QR Code was not found in the image. " +
"We detected that the decoding process took quite a while. " +
"It will be much faster if you decrease your camera's resolution (640x480).");
} else {
showMessage("A QR Code was not found in the image.");
}
return;
}
if (result != null) {
String resultText = result.getText();
Log.info("result: " + resultText);
if (isURI(resultText)) {
resultText = URLDecoder.decode(resultText);
removeProgressBar();
invalidate();
if (!decodingTimer.wasResonableTime() &&
!AppSettings.getInstance().getBooleanItem(AppSettings.SETTING_CAM_RES_MSG).booleanValue()) {
showMessage("We detected that the decoding process took quite a while. " +
"It will be much faster if you decrease your camera's resolution (640x480).");
}
DecodeHistory.getInstance().addHistoryItem(new DecodeHistoryItem(resultText));
invokeBrowser(resultText);
return;
}
} else {
removeProgressBar();
invalidate();
showMessage("A QR Code was not found in the image.");
return;
}
}
removeProgressBar();
invalidate();
}
/**
* Quick check to see if the result of decoding the qr code was a valid uri.
*/
private boolean isURI(String uri) {
return uri.startsWith("http://");
}
/**
* Invokes the web browser and browses to the given uri.
*/
private void invokeBrowser(String uri) {
BrowserSession browserSession = Browser.getDefaultSession();
browserSession.displayPage(uri);
}
/**
* Syncronized version of removing progress dialog.
* NOTE: All methods accessing the gui that are in seperate threads should syncronize on app.getEventLock()
*/
private void removeProgressBar() {
synchronized (app.getAppEventLock()) {
if (popup != null) {
app.popScreen(popup);
}
}
}
/**
* Syncronized version of showing a message dialog.
* NOTE: All methods accessing the gui that are in seperate threads should syncronize on app.getEventLock()
*/
private void showMessage(String message) {
synchronized (app.getAppEventLock()) {
Dialog.alert(message);
}
}
}
}